package com.cb.common.security.filter.chain;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.util.AntUrlPathMatcher;
import org.springframework.security.util.UrlMatcher;

import com.cb.core.util.RequestUtil;


public class SecurityFilterChain implements Filter {

    private static final Log logger = LogFactory.getLog(SecurityFilterChain.class);
    
    private Map<String, Filter> filters = new HashMap();
    
    private List<String> noneInterceptUrls = new ArrayList();
    
    private boolean stripQueryStringFromUrls = true;
    private UrlMatcher matcher = new AntUrlPathMatcher();

	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
			ServletException {

		HttpServletRequest request = (HttpServletRequest)req;
		HttpServletResponse response = (HttpServletResponse)resp;
		String requestUrl = RequestUtil.getRequestUrl(request);
		
		boolean isNoneInterceptUrl = isNoneInterceptUrl(requestUrl);
		if (isNoneInterceptUrl) {
            if (logger.isDebugEnabled()) {
                logger.debug("None intercept url: "+requestUrl);
            }
            chain.doFilter(request, response);
            return;
		}
        
		
        List<Filter> matchFilters = getMatchFilters(requestUrl);
        if (matchFilters == null || matchFilters.size() == 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(requestUrl +
                		matchFilters == null ? " has no matching filters" : " has an empty filter list");
            }
            chain.doFilter(request, response);
            return;
        }

        VirtualFilterChain virtualFilterChain = new VirtualFilterChain(chain, matchFilters);
        virtualFilterChain.doFilter(request, response);
	}
	
    private boolean isNoneInterceptUrl(String requestUrl) {
		for (String noneInterceptUrl : noneInterceptUrls) {
			if (doMatch(noneInterceptUrl, requestUrl))
				return true;
		}
		return false;
	}

	public List<Filter> getMatchFilters(String url)  {
    	List<Filter> matchFilters = new LinkedList();
    	
        if (stripQueryStringFromUrls) {
            int firstQuestionMarkIndex = url.indexOf("?");

            if (firstQuestionMarkIndex != -1) {
                url = url.substring(0, firstQuestionMarkIndex);
            }
        }

        for (Iterator<String> iterator = filters.keySet().iterator(); iterator.hasNext();) {
			String path = iterator.next();
			
			boolean matched = doMatch(path, url);

            if (matched) {
            	matchFilters.add(filters.get(path));
            }
        }

        return matchFilters;
    }

	private boolean doMatch(String path, String url) {
        if (matcher.requiresLowerCaseUrl()) {
            url = url.toLowerCase();

            if (logger.isDebugEnabled()) {
                logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
            }
        }

        boolean matched = matcher.pathMatchesUrl(path, url);

        if (logger.isDebugEnabled()) {
            logger.debug("Candidate is: '" + url + "'; pattern is " + path + "; matched=" + matched);
        }
        return matched;
	}

	public void init(FilterConfig arg0) throws ServletException {
	}

	public void destroy() {
	}

	public void setFilters(Map<String, Filter> filters) {
		this.filters = filters;
	}
	
    private static class VirtualFilterChain implements FilterChain {
        private FilterChain chain;
        
        private List<Filter> additionalFilters;
        private int currentPosition = 0;

        private VirtualFilterChain(FilterChain chain, List<Filter> additionalFilters) {
            this.chain = chain;
            this.additionalFilters = additionalFilters;
        }

        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        	String requestUrl = RequestUtil.getRequestUrl((HttpServletRequest)request);
        	
            if (currentPosition == additionalFilters.size()) {
                if (logger.isDebugEnabled()) {
                    logger.debug(requestUrl
                        + " reached end of additional filter chain; proceeding with original chain");
                }

                chain.doFilter(request, response);
            } else {
                currentPosition++;

                Filter nextFilter = additionalFilters.get(currentPosition - 1);

                if (logger.isDebugEnabled()) {
                    logger.debug(requestUrl + " at position " + currentPosition + " of "
                        + additionalFilters.size() + " in additional filter chain; firing Filter: '"
                        + nextFilter + "'");
                }

               nextFilter.doFilter(request, response, this);
            }
        }
    }

	public void setStripQueryStringFromUrls(boolean stripQueryStringFromUrls) {
		this.stripQueryStringFromUrls = stripQueryStringFromUrls;
	}

	public void setNoneInterceptUrls(List<String> noneInterceptUrls) {
		this.noneInterceptUrls = noneInterceptUrls;
	}

}
